#include <buola/term.h>
#include <buola/term/cvirtualterm.h>
#include <buola/gui/events.h>
#include <buola/io/filesystem.h>
#include <buola/io/cfdstream.h>

#if BUOLA_PLATFORM_MAC
#include <util.h>
#else
#include <pty.h>
#endif
#include <unicode/utf8.h>
#include <sys/stat.h>
#include <sys/ioctl.h>

#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"

namespace buola {

using namespace gui;

static CVirtualTerm::SCell sDefaultCell=
{
    L' ',
    TERMATTR_DEFAULT
};

CVirtualTerm::CVirtualTerm(int pR,int pC)
    :   mSomeDirty(false)
    ,   mTitle(L"shell"),
        mExited(false),
        mLines(4096),
        mSize(pR,pC),
        mCursor(0,0),
        mOrigin(0,0),
        mCurAttr(TERMATTR_DEFAULT),
        mEscaped(false),
        mGraphMode(false),
        mUTF8(false),
        mScrollTop(0),
        mScrollBottom(pR-1),
        mMode(MODE_AUTOWRAP|MODE_SHOW_CURSOR)
{
    for(int i=0;i<mSize.r;i++)
    {
        mLines.push_back(NewEmptyLine());
    }
}

CVirtualTerm::~CVirtualTerm()
{
}

void CVirtualTerm::ForkPty(const std::string &pCmd)
{
    struct winsize lWS;

    pid_t lChildPID;

    lWS.ws_row=mSize.r;
    lWS.ws_col=mSize.c;
    lWS.ws_xpixel=0;
    lWS.ws_ypixel=0;

    int lPty;
    lChildPID=forkpty(&lPty,nullptr,nullptr,&lWS);

    if(lChildPID<0)
        throw XIO("can't open pty");

    if(lChildPID==0)
    {
        //in the child
        umask((mode_t)io::get_umask());
        setenv("TERM","xterm",1);

        execl("/bin/sh","/bin/sh","-c",pCmd.c_str(),nullptr);

        msg_error() << "can't execute command\n";

        exit(127);
    }

    //in the parent
    mChildPID=lChildPID;

    mPty=new io::CFDStream(lPty);
    mPty->SetActivityDelegate(this,io::ENotify::READ);
}

void CVirtualTerm::OnStreamCanRead(io::CFDStream *pStream)
{
    char lBuf[32768];

    if(!mPty->IsOpen()) return;
    try
    {
        size_t lBytes=mPty->Read((uint8_t*)lBuf,32768).Count();
        if(!lBytes) return;
        Inject(lBuf,lBytes);
    }
    catch(...)
    {
        msg_warn() << "can't read from pty\n";
        mPty->SetActivityDelegate(nullptr,io::ENotify::NONE);
        mExited=true;
        sExited();
    }
}

void CVirtualTerm::Disconnect()
{
    mPty->Close();
    mChildPID=0;
}

void CVirtualTerm::Inject(const char *pBuf,int pSize)
{
    mSomeDirty=false;
    
    for(;pSize;pSize--,pBuf++)
    {
        unsigned char lC=*pBuf;

        if(lC==0||lC==127) continue;    //totally ignore NUL and DEL
        if(mEscaped)
        {
            if(lC=='\n'||lC=='\r'||lC=='\b'||lC=='\t'||lC==0x0b||lC==0x0c)
            {
                ProcessControlChar(lC);
            }
            else if(lC==0x18||lC==0x1A)
            {
                CancelEscapeSequence();
            }
            else if(mEscBuf.size()<1024)
            {
                mEscBuf.push_back(lC);
                InterpretEscapeSequence();
            }
            else
            {
                msg_warn() << "discarding because too long\n";
                CancelEscapeSequence();
            }

            continue;
        }

        if(mUTF8)
        {
            if((lC&0xc0)==0x80) //it is a trail byte... ok
            {
                mUTF8Buf.push_back(lC);

                if(U8_COUNT_TRAIL_BYTES((uint8_t)mUTF8Buf[0])==mUTF8Buf.size()-1)
                {
                    PutNormalChar(utf32(mUTF8Buf)[0]);
                    mUTF8=false;
                }
                continue;
            }
            else //it probably wasn't utf8... put previous, process this one
            {
                PutNormalChar((unsigned char)mUTF8Buf[0]);
                mUTF8=false;
            }
        }

        if(lC>=1&&lC<=31)
        {
            ProcessControlChar(lC);
        }
        else if(mGraphMode)
        {
            PutGraphmodeChar(lC);
        }
        else if(lC>=0xc0&&lC<0xfe)
        {
            mUTF8=true;
            mUTF8Buf.clear();
            mUTF8Buf.push_back(lC);
        }
        else
        {
            PutNormalChar(lC);
        }
    }

    if(mSomeDirty)
        sDirty(mFirstDirty,mLastDirty);
}

void CVirtualTerm::Write(const char *pBuf,int pSize)
{
    if(!mPty->IsOpen())
    {
        Inject(pBuf,pSize);
        return;
    }

    mPty->Write((const uint8_t*)pBuf,pSize);
}

void CVirtualTerm::Write(const std::string &pStr)
{
    Write(pStr.data(),pStr.length());
}

void CVirtualTerm::Resize(int pR,int pC)
{
    if(pR!=mSize.r)
    {
        if(mScrollTop==0&&mScrollBottom==mSize.r-1)
        {
            mScrollBottom=pR-1;
        }
        else
        {
            mScrollBottom=std::min(mScrollBottom,pR-1);
            mScrollTop=std::min(mScrollTop,mScrollBottom);
        }

        ResizeRows(pR);
        sSizeChanged();
    }

    if(pC!=mSize.c)
    {
        ResizeCols(pC);
    }

    struct winsize lSize;
    lSize.ws_row=mSize.r;
    lSize.ws_col=mSize.c;
    ioctl(mPty->FD(),TIOCSWINSZ,&lSize);

    sDirtyAll();
}

bool CVirtualTerm::ProcessKeyEvent(const gui::CKeyEvent &pE)
{
    if(mMode&MODE_NETHACK)
    {
        switch(pE.mKey.GetCode())
        {
        case KEY_UP:
        case KEY_KPUP:
        case KEY_KP8:
            if(pE.mMods&KEYMOD_SHIFT)
                Write("K");
            else
                Write("k");
            return true;
        case KEY_DOWN:
        case KEY_KPDOWN:
        case KEY_KP2:
            if(pE.mMods&KEYMOD_SHIFT)
                Write("J");
            else
                Write("j");
            return true;
        case KEY_RIGHT:
        case KEY_KPRIGHT:
        case KEY_KP6:
            if(pE.mMods&KEYMOD_SHIFT)
                Write("L");
            else
                Write("l");
            return true;
        case KEY_LEFT:
        case KEY_KPLEFT:
        case KEY_KP4:
            if(pE.mMods&KEYMOD_SHIFT)
                Write("H");
            else
                Write("h");
            return true;
        case KEY_HOME:
        case KEY_KPHOME:
        case KEY_KP7:
            if(pE.mMods&KEYMOD_SHIFT)
                Write("Y");
            else
                Write("y");
            return true;
        case KEY_PGUP:
        case KEY_KPPGUP:
        case KEY_KP9:
            if(pE.mMods&KEYMOD_SHIFT)
                Write("U");
            else
                Write("u");
            return true;
        case KEY_END:
        case KEY_KPEND:
        case KEY_KP1:
            if(pE.mMods&KEYMOD_SHIFT)
                Write("B");
            else
                Write("b");
            return true;
        case KEY_PGDN:
        case KEY_KPPGDN:
        case KEY_KP3:
            if(pE.mMods&KEYMOD_SHIFT)
                Write("N");
            else
                Write("n");
            return true;
        case KEY_KP5:
            Write(".");
            return true;
        default:
            break;
        }
    }

    switch(pE.mKey.GetCode())
    {
    case KEY_ESC:
        Write("\e");
        return true;
    case KEY_UP:
    case KEY_KPUP:
        WriteCursorKey("A",pE.mMods);
        return true;
    case KEY_DOWN:
    case KEY_KPDOWN:
        WriteCursorKey("B",pE.mMods);
        return true;
    case KEY_RIGHT:
    case KEY_KPRIGHT:
        WriteCursorKey("C",pE.mMods);
        return true;
    case KEY_LEFT:
    case KEY_KPLEFT:
        WriteCursorKey("D",pE.mMods);
        return true;
    case KEY_HOME:
    case KEY_KPHOME:
        WriteModKey("\eO","H",pE.mMods,true);
        return true;
    case KEY_END:
    case KEY_KPEND:
        WriteModKey("\eO","F",pE.mMods,true);
        return true;
    case KEY_BACKSPACE:
        {
            char lC=(char)127;
            Write(&lC,1);
        }
        return true;
    case KEY_0 ... KEY_9:
        if(pE.mMods&KEYMOD_ALT)
        {
            std::string lString("\e");
            lString+=(char)(pE.mKey.GetCode()-KEY_0)+'0';
            Write(lString);
            return true;
        }
        return false;
    case KEY_A ... KEY_Z:
        if(pE.mMods&KEYMOD_ALT)
        {
            if(pE.mKey.GetCode()==KEY_N&&pE.mMods&KEYMOD_CONTROL)
            {
                if(mMode&MODE_NETHACK)
                    mMode&=~MODE_NETHACK;
                else
                    mMode|=MODE_NETHACK;
                sTitleChanged();
            }
            else
            {
                std::string lString("\e");
                if(pE.mMods&KEYMOD_SHIFT)
                    lString+=(char)(pE.mKey.GetCode()-KEY_A)+'A';
                else
                    lString+=(char)(pE.mKey.GetCode()-KEY_A)+'a';
                Write(lString);
            }
            return true;
        }
        else if(pE.mMods&KEYMOD_CONTROL)
        {
            char lC=(char)(pE.mKey.GetCode()-KEY_A)+1;
            Write(&lC,1);
            return true;
        }
        return false;
    case KEY_INS:
        WriteModKey("\e[2","~",pE.mMods,false);
        return true;
    case KEY_DEL:
        WriteModKey("\e[3","~",pE.mMods,false);
        return true;
    case KEY_PGUP:
        WriteModKey("\e[5","~",pE.mMods,false);
        return true;
    case KEY_PGDN:
        WriteModKey("\e[6","~",pE.mMods,false);
        return true;
    case KEY_F1:
        WriteModKey("\eO","P",pE.mMods,true);
        return true;
    case KEY_F2:
        WriteModKey("\eO","Q",pE.mMods,true);
        return true;
    case KEY_F3:
        WriteModKey("\eO","R",pE.mMods,true);
        return true;
    case KEY_F4:
        WriteModKey("\eO","S",pE.mMods,true);
        return true;
    case KEY_F5:
        WriteModKey("\e[15","~",pE.mMods,false);
        return true;
    case KEY_F6:
        WriteModKey("\e[17","~",pE.mMods,false);
        return true;
    case KEY_F7:
        WriteModKey("\e[18","~",pE.mMods,false);
        return true;
    case KEY_F8:
        WriteModKey("\e[19","~",pE.mMods,false);
        return true;
    case KEY_F9:
        WriteModKey("\e[20","~",pE.mMods,false);
        return true;
    case KEY_F10:
        WriteModKey("\e[21","~",pE.mMods,false);
        return true;
    case KEY_F11:
        WriteModKey("\e[23","~",pE.mMods,false);
        return true;
    case KEY_F12:
        WriteModKey("\e[24","~",pE.mMods,false);
        return true;
    case KEY_ENTER:
    case KEY_KPENTER:
        Write("\r",1);
        return true;
    case KEY_TAB:
        if(pE.mMods&KEYMOD_SHIFT)
            Write("\e[Z",3);
        else
            Write("\t",1);
        return true;
    default:
        break;
    }
    
    return false;
}

void CVirtualTerm::ProcessMouseEvent(const CCursorPos &pPos,const CMouseEvent &pE)
{
    int lButton;

    if(mMode&MODE_X10_MOUSE)
    {
        if(pE.mType!=CMouseEvent::PRESS&&pE.mType!=CMouseEvent::WHEEL) return;

        if(pE.mType==CMouseEvent::WHEEL)
        {
            if(pE.mScrollY<0.0)
                lButton=3;
            else if(pE.mScrollY>0.0)
                lButton=4;
            else
                return;
        }
        else if(pE.mType==CMouseEvent::PRESS)
        {
            switch(pE.mButton)
            {
            case EMouseButton::LEFT:
                lButton=0;
                break;
            case EMouseButton::MIDDLE:
                lButton=1;
                break;
            case EMouseButton::RIGHT:
                lButton=2;
                break;
            default:
                return;
            }
        }
        else
        {
            return;
        }
    }
    else if(pE.mType==CMouseEvent::PRESS||pE.mType==CMouseEvent::RELEASE)
    {
        if(pE.mType==CMouseEvent::RELEASE)
        {
            lButton=3;
            mSentMouseButton=-1;
        }
        else
        {
            switch(pE.mButton)
            {
            case EMouseButton::LEFT:
                lButton=0;
                break;
            case EMouseButton::MIDDLE:
                lButton=1;
                break;
            case EMouseButton::RIGHT:
                lButton=2;
                break;
            default:
                return;
            }
            mSentMouseButton=lButton;
            mSentMousePos=pPos;
        }
    }
    else if(pE.mType==CMouseEvent::WHEEL)
    {
        if(pE.mScrollY<0.0)
            lButton=64;
        else if(pE.mScrollY>0.0)
            lButton=65;
        else
            return;
        mSentMouseButton=lButton;
        mSentMousePos=pPos;
    }
    else if(pE.mType==CMouseEvent::MOTION)
    {
        if(mMode&MODE_ANY_EVENT_MOUSE)
        {
            if(mSentMousePos==pPos) return;
            lButton=32;
            mSentMousePos=pPos;
        }
        else if(mSentMouseButton>=0&&(mMode&MODE_BTN_EVENT_MOUSE))
        {
            if(mSentMousePos==pPos) return;
            lButton=mSentMouseButton+32;
            mSentMousePos=pPos;
        }
        else
        {
            return;
        }
    }
    else
    {
        return;
    }

    unsigned char lWrite[6]="\e[M";

    lWrite[3]=(unsigned char)32+(unsigned char)lButton;
    lWrite[4]=(unsigned char)33+(unsigned char)pPos.c;
    lWrite[5]=(unsigned char)33+(unsigned char)pPos.r;

    Write((char*)lWrite,6);
}

void CVirtualTerm::WriteCursorKey(const char *pCode,EKeyMod pMod)
{
    if(mMode&MODE_CURSOR_APP&&pMod==KEYMOD_NONE)
        Write(std::string("\eO")+pCode);
    else
        WriteModKey("\e[",pCode,pMod,true);
}

void CVirtualTerm::WriteModKey(const char *pPrefix,const char *pSuffix,EKeyMod pMod,bool pAddOne)
{
    if(!(pMod&(KEYMOD_SHIFT|KEYMOD_ALT|KEYMOD_CONTROL)))
    {
        Write(std::string(pPrefix)+pSuffix);
    }
    else
    {
        char lCode=0;
        if(pMod&KEYMOD_SHIFT) lCode|=1;
        if(pMod&KEYMOD_ALT) lCode|=2;
        if(pMod&KEYMOD_CONTROL) lCode|=4;
        std::string lString(pPrefix);
        if(pAddOne) lString+='1';
        lString+=';';
        lString+='1'+lCode;
        Write(lString+pSuffix);
    }
}

CVirtualTerm::SLine *CVirtualTerm::NewEmptyLine()
{
    SLine *lLine=new SLine;
    lLine->mCells.resize(mSize.c,sDefaultCell);
    lLine->mCmdStart=false;
    
    return lLine;
}

void CVirtualTerm::SetCursorR(int pR)
{
    DirtyLine(mCursor.r);
    mCursor.r=clamp(pR,mOrigin.r,(mMode&MODE_ORIGIN)?
                                      mScrollBottom:(mSize.r-1));
    DirtyLine(mCursor.r);
}

void CVirtualTerm::SetCursorC(int pC)
{
    DirtyLine(mCursor.r);
    mCursor.c=clamp(pC,mOrigin.c,mSize.c-1);
}

void CVirtualTerm::CursorDown()
{
    DirtyLine(mCursor.r);
    mCursor.r++;
    if(mCursor.r<=mScrollBottom)
    {
        DirtyLine(mCursor.r);
        return;
    }

    mCursor.r=mScrollBottom;

    //two scenarios here

    //scrolling is normal, we add a line at the end
    if(mScrollTop==0&&mScrollBottom==mSize.r-1)
    {
        mLines.push_back(NewEmptyLine());

        DirtyLines(0,mSize.r-1);
        sSizeChanged();
    }
    else //scrolling is restricted, rotate those lines
    {
        TLineIter lIt=mLines.end()-mSize.r;

        std::rotate(lIt+mScrollTop,lIt+mScrollTop+1,lIt+mScrollBottom+1);

        *(lIt+mScrollBottom)=NewEmptyLine();

        DirtyLines(mScrollTop,mScrollBottom);
    }
}

void CVirtualTerm::CursorUp()
{
    DirtyLine(mCursor.r);
    mCursor.r--;

    if(mCursor.r>=mScrollTop)
    {
        DirtyLine(mCursor.r);
        return;
    }
    mCursor.r=mScrollTop;

    TLineIter lIt=mLines.end()-mSize.r;

    std::rotate(lIt+mScrollTop,lIt+mScrollBottom,lIt+mScrollBottom+1);

    *(lIt+mScrollTop)=NewEmptyLine();

    DirtyLines(mScrollTop,mScrollBottom);
}

void CVirtualTerm::NewEscapeSequence()
{
    mEscaped=true;
    mEscBuf.clear();
}

void CVirtualTerm::CancelEscapeSequence()
{
    mEscaped=false;
}

bool IsValidCSIEnder(char pC)
{
    return (pC>='a'&&pC<='z')||
           (pC>='A'&&pC<='Z')||
           pC=='@'||pC=='`';
}

void CVirtualTerm::InterpretCSISequence()
{
    const char *lP=mEscBuf.c_str()+1;
    char lVerb=mEscBuf[mEscBuf.size()-1];
    bool lPrivate=false;

    if(*lP=='?') //private mode
    {
        lPrivate=true;
        lP++;
    }
    else if(*lP=='>'||*lP=='='||*lP=='!')
    {
        msg_warn() << "unknown CSI modifier\n";
        return;
    }

    std::vector<int> lParams;

    //parse numeric parameters
    while((*lP>='0'&&*lP<='9')||*lP==';')
    {
        if(*lP==';')
        {
            lParams.push_back(0);
        }
        else
        {
            if(lParams.empty()) lParams.push_back(0);

            lParams.back()=lParams.back()*10+(*lP-'0');
        }

        lP++;
    }

    if(*lP!=lVerb)
    {
        msg_warn() << "misinterpreted sequence " << mEscBuf << "\n";
        for(int i=0;i<mEscBuf.size();i++)
        {
            msg_info() << (int)(unsigned char)mEscBuf[i] << "\n";
        }
    }

    if(lVerb=='D') // CUB -- Cursor backward
    {
        int lN=(lParams.size()&&lParams[0]>0)?lParams[0]:1;

        SetCursorC(mCursor.c-lN);
    }
    else if(lVerb=='B' // CUD -- Cursor Down
          ||lVerb=='e')// VPR -- Vertical Position Relative
    {
        int lN=(lParams.size()&&lParams[0]>0)?lParams[0]:1;

        SetCursorR(mCursor.r+lN);
    }
    else if(lVerb=='C' // CUF -- Cursor Forward
          ||lVerb=='a')// HPR -- Horizontal Position Relative
    {
        int lN=(lParams.size()&&lParams[0]>0)?lParams[0]:1;

        SetCursorC(mCursor.c+lN);
    }
    else if(lVerb=='H'      //CUP -- Cursor Position
          ||lVerb=='f')     //HVP -- Horizontal and Vertical Position
    {
        if(!lParams.size())
        {
            SetCursorR(mOrigin.r);
            SetCursorC(mOrigin.c);
        }
        else if(lParams.size()>=2)
        {
            SetCursorR(mOrigin.r+lParams[0]-1);
            SetCursorC(mOrigin.c+lParams[1]-1);
        }
    }
    else if(lVerb=='A') //CUU -- Cursor Up
    {
        int lN=(lParams.size()&&lParams[0]>0)?lParams[0]:1;

        SetCursorR(mCursor.r-lN);
    }
    else if(lVerb=='c') //DA -- Device attributes
    {
        switch(lParams.size()?lParams[0]:0)
        {
        case 0:
            Write("\e[?62;9;c",9);  //that's what gnome-terminal does
            break;
        default:
            msg_warn() << "unimplemented device attributes request\n";
        }
    }
    else if(lVerb=='q') //DECLL -- Load LEDS
    {
        msg_warn() << "unimplemented CSI q\n";
    }
    else if(lVerb=='x') //DECREPTPARM -- Report Terminal Parameters
    {
        msg_warn() << "unimplemented CSI x\n";
    }
    else if(lVerb=='r') //DECSTBM -- Set Top and Bottom Margins
    {
        if(!lParams.size())
        {
            mScrollTop=0;
            mScrollBottom=mSize.r-1;
        }
        else if(lParams.size()>=2)
        {
            mScrollTop=std::max(0,lParams[0]-1);
            mScrollBottom=std::min(mSize.r-1,lParams[1]-1);
        }

        if(mMode&MODE_ORIGIN)
        {
            mOrigin.r=mScrollTop;

            SetCursorR(mCursor.r);
        }
    }
    else if(lVerb=='y') //DECTST -- Invoke Confidence Test
    {
        msg_warn() << "unimplemented CSI y\n";
    }
    else if(lVerb=='n') //DSR -- Device Status Report
    {
        msg_warn() << "unimplemented CSI n\n";
    }
    else if(lVerb=='J') //ED -- Erase In Display
    {
        int lSR,lSC,lER,lEC;

        if(lParams.size()&&lParams[0]==2)
        {
            lSR=0;
            lSC=0;
            lER=mSize.r-1;
            lEC=mSize.c-1;
        }
        else if(lParams.size()&&lParams[0]==1)
        {
            lSR=0;
            lSC=0;
            lER=mCursor.r;
            lEC=mCursor.c;
        }
        else
        {
            lSR=mCursor.r;
            lSC=mCursor.c;
            lER=mSize.r-1;
            lEC=mSize.c-1;
        }

        for(int r=lSR;r<=lER;r++)
        {
            PLine lLine=GetLine(r);
            lLine->mCmdStart=false;

            for(int c=(r==lSR?lSC:0);c<=(r==lER?lEC:mSize.c-1);c++)
            {
                lLine->mCells[c].mC=' ';
                lLine->mCells[c].mAttr=mCurAttr;
            }

            DirtyLine(r);
        }
    }
    else if(lVerb=='K') //EL -- Erase In Line
    {
        int lS,lE;

        switch(lParams.size()?lParams[0]:0)
        {
        case 1:
            lS=0;
            lE=mCursor.c;
            break;
        case 2:
            lS=0;
            lE=mSize.c-1;
            break;
        default:
            lS=mCursor.c;
            lE=mSize.c-1;
        }

        PLine lLine=GetLine(mCursor.r);

        for(int i=lS;i<=lE;i++)
        {
            lLine->mCells[i].mC=' ';
            lLine->mCells[i].mAttr=mCurAttr;
        }

        DirtyLine(mCursor.r);
    }
    else if(lVerb=='l'  //RM -- Reset Mode
          ||lVerb=='h') //SM -- Set Mode
    {
        EMode lMode=MODE_NONE;

        for(int i=0;i<lParams.size();i++)
        {
            if(lPrivate)
            {
                switch(lParams[i])
                {
                case 1:
                    lMode|=MODE_CURSOR_APP;
                    break;
                case 7:
                    lMode|=MODE_AUTOWRAP;
                    break;
                case 12:
                    lMode|=MODE_BLINK_CURSOR;
                    break;
                case 25:
                    lMode|=MODE_SHOW_CURSOR;
                    break;
                case 1049:
                    lMode|=MODE_ALTERNATE;
                    break;
                case 3:
                    for(int r=0;r<mSize.r;r++)
                    {
                        PLine lLine=GetLine(r);
                        for(int c=0;c<mSize.c;c++)
                        {
                            lLine->mCells[c]=sDefaultCell;
                        }
                        DirtyLine(r);
                    }
                    mCursor=mOrigin;
                    break;
                case 2:
                case 4:
                case 5:
                case 8:
                    break; //safely ignore these
                case 6:
                    lMode|=MODE_ORIGIN;
                    break;
                case 9:
                    lMode|=MODE_X10_MOUSE;
                    break;
                case 1000:
                    lMode|=MODE_VT200_MOUSE;
                    break;
                case 1001:
                    lMode|=MODE_VT200_HIGHLIGHT_MOUSE;
                    break;
                case 1002:
                    lMode|=MODE_BTN_EVENT_MOUSE;
                    break;
                case 1003:
                    lMode|=MODE_ANY_EVENT_MOUSE;
                    break;
                case 1004:
                    lMode|=MODE_FOCUS_EVENT_MOUSE;
                    break;
                default:
                    msg_warn() << "unknown private mode " << lParams[i] << lVerb << "\n";
                }
            }
            else
            {
                switch(lParams[i])
                {
                case 4:
                    lMode|=MODE_INSERT;
                    break;
                case 20:
                    lMode|=MODE_LINEFEED;
                    break;
                default:
                    msg_warn() << "unknown mode " << lParams[i] << lVerb << "\n";
                }
            }
        }

        EMode lOld=mMode;

        if(lVerb=='h')
        {
            mMode|=lMode;
        }
        else
        {
            mMode&=~lMode;
        }

        EMode lDiff=mMode^lOld;

        if(lDiff&MODE_ALTERNATE)
        {
            SwitchAlternate();
        }
        else if(lDiff&MODE_ORIGIN)
        {
            if(mMode&MODE_ORIGIN)
            {
                mOrigin.r=mScrollTop;
            }
            else
            {
                mOrigin.r=0;
            }

            SetCursorR(mOrigin.r);
            SetCursorC(mOrigin.c);
        }
        else if(lDiff&MODE_WANTS_MOUSE_EVENTS)
        {
            mSentMouseButton=-1;
        }
    }
    else if(lVerb=='m') //SGR -- Select Graphic Rendition
    {
        if(lParams.empty()) //reset attributes
        {
            mCurAttr=TERMATTR_DEFAULT;
        }
        else
        {
            for(int i=0;i<lParams.size();i++)
            {
                switch(lParams[i])
                {
                case 0:
                    mCurAttr=TERMATTR_DEFAULT;
                    break;
                case 1:
                case 2:
                case 4:
                    TERMATTR_SETBOLD(mCurAttr);
                    break;
                case 5:
                    TERMATTR_SETBLINK(mCurAttr);
                    break;
                case 7:
                    TERMATTR_SETREVERSE(mCurAttr);
                    break;
                case 27:
                    TERMATTR_UNSETREVERSE(mCurAttr);
                    break;
                case 8:
                    mCurAttr=TERMATTR_INVISIBLE;
                    break;
                case 22:
                case 24:
                    TERMATTR_UNSETBOLD(mCurAttr);
                    break;
                case 25:
                    TERMATTR_UNSETBLINK(mCurAttr);
                    break;
                case 28:
                    mCurAttr=TERMATTR_DEFAULT;
                    break;
                case 30 ... 37:
                    TERMATTR_SETFORECOLOR(mCurAttr,lParams[i]-30);
                    break;
                case 40 ... 47:
                    TERMATTR_SETBACKCOLOR(mCurAttr,lParams[i]-40);
                    break;
                case 39:
                    TERMATTR_SETFORECOLOR(mCurAttr,7);
                    break;
                case 49:
                    TERMATTR_SETBACKCOLOR(mCurAttr,0);
                    break;
                case 10:
                    break;
                default:
                    msg_warn() << "wrong params in csi sequence " << lParams[i] << "\n";
                }
            }
        }
    }
    else if(lVerb=='g') //TBC -- Tabulation Clear
    {
        if(!lParams.size()||lParams[0]==0)
        {
            for(int i=0;i<mTabStops.size();i++)
            {
                if(mTabStops[i]==mCursor.c)
                {
                    mTabStops.erase(mTabStops.begin()+i);
                    break;
                }
            }
        }
        else if(lParams[0]==3)
            mTabStops.clear();
    }
    else if(lVerb=='P') //DCH -- Delete Character
    {
        int lN=(lParams.size()&&lParams[0]>0)?lParams[0]:1;

        PLine lLine=GetLine(mCursor.r);

        for(int i=mCursor.c;i<mSize.c;i++)
        {
            if(i+lN<mSize.c)
            {
                lLine->mCells[i]=lLine->mCells[i+lN];
            }
            else
            {
                lLine->mCells[i].mC=' ';
                lLine->mCells[i].mAttr=mCurAttr;
            }
        }

        DirtyLine(mCursor.r);
    }
    else if(lVerb=='F') // CPL - Cursor Previous Line
    {
        int lN=(lParams.size()&&lParams[0]>0)?lParams[0]:1;

        SetCursorR(mCursor.r-lN);
        SetCursorC(0);
    }
    else if(lVerb=='E') // CNL - Cursor Next Line
    {
        int lN=(lParams.size()&&lParams[0]>0)?lParams[0]:1;

        SetCursorR(mCursor.r+lN);
    }
    else if(lVerb=='G'  //CHA -- Cursor Horizontal Absolute
          ||lVerb=='`') //HPA -- Horizontal Position Absolute
    {
        int lN=(lParams.size()&&lParams[0]>0)?lParams[0]:1;

        SetCursorC(mOrigin.c+lN-1);
    }
    else if(lVerb=='d') //VPA -- Vertical Position Absolute
    {
        int lN=(lParams.size()&&lParams[0]>0)?lParams[0]:1;

        SetCursorR(mOrigin.r+lN-1);
    }
    else if(lVerb=='X') //ECH -- Erase Character
    {
        int lN=(lParams.size()&&lParams[0]>0)?lParams[0]:1;

        PLine lLine=GetLine(mCursor.r);

        for(int i=mCursor.c;i<mCursor.c+lN&&i<mSize.c;i++)
        {
            lLine->mCells[i].mC=' ';
            lLine->mCells[i].mAttr=TERMATTR_DEFAULT;
        }

        DirtyLine(mCursor.r);
    }
    else if(lVerb=='@') //ICH -- Insert Characters
    {
        int lN=(lParams.size()&&lParams[0]>0)?lParams[0]:1;

        PLine lLine=GetLine(mCursor.r);

        for(int i=mSize.c-1;i>=mCursor.c+lN;i--)
            lLine->mCells[i]=lLine->mCells[i-lN];
        for(int i=mCursor.c;i<mCursor.c+lN;i++)
        {
            lLine->mCells[i].mC=' ';
            lLine->mCells[i].mAttr=TERMATTR_DEFAULT;
        }

        DirtyLine(mCursor.r);
    }
    else if(lVerb=='L') //IL -- Insert Line
    {
        if(mCursor.r<mScrollTop||mCursor.r>mScrollBottom)
            return;

        int lN=(lParams.size()&&lParams[0]>0)?lParams[0]:1;

        TLineIter lIt=mLines.end()-mSize.r;

        lN=std::min(lN,mScrollBottom-mCursor.r+1);

        std::rotate(lIt+mCursor.r,lIt+mScrollBottom+1-lN,lIt+mScrollBottom+1);

        for(int i=mCursor.r;i<mCursor.r+lN;i++)
            *(lIt+i)=NewEmptyLine();

        DirtyLines(mCursor.r,mScrollBottom);
    }
    else if(lVerb=='M') //DL -- Delete Line
    {
        if(mCursor.r<mScrollTop||mCursor.r>mScrollBottom)
            return;

        int lN=(lParams.size()&&lParams[0]>0)?lParams[0]:1;

        TLineIter lIt=mLines.end()-mSize.r;

        lN=std::min(lN,mScrollBottom-mCursor.r+1);

        std::rotate(lIt+mCursor.r,lIt+mCursor.r+lN,lIt+mScrollBottom+1);

        for(int i=mScrollBottom-lN+1;i<=mScrollBottom;i++)
            *(lIt+i)=NewEmptyLine();

        DirtyLines(mCursor.r,mScrollBottom);
    }
    else
    {
        msg_warn() << "unimplemented csi sequence " << lVerb << "\n";
    }
}

void CVirtualTerm::InterpretXTermSequence()
{
    int i=1;

    int lParam=0;

    //parse numeric parameters
    while(mEscBuf[i]>='0'&&mEscBuf[i]<='9')
    {
        lParam=lParam*10+(mEscBuf[i]-'0');
        i++;
    }

    if(mEscBuf[i]!=';')
    {
        msg_warn() << "wrong xterm sequence\n";
        return;
    }

    i++;

    if(lParam==0||lParam==2)
    {
        GetLine(mCursor.r)->mCmdStart=true;
        mTitle=utf32(mEscBuf.substr(i,mEscBuf.size()-i-1));
        sTitleChanged();
    }
}

void CVirtualTerm::InterpretEscapeSequence()
{
    if(mEscBuf.empty()) return;

    if(mEscBuf[0]=='M')
    {
        CursorUp();
        CancelEscapeSequence();
    }
    else if(mEscBuf[0]=='E') //NEL -- New Line
    {
        CursorDown();
        mCursor.c=0;
        CancelEscapeSequence();
    }
    else if(mEscBuf[0]=='7') //Save cursor and attributes
    {
        mSaved.mCursor=mCursor;
        mSaved.mCurAttr=mCurAttr;
        CancelEscapeSequence();
    }
    else if(mEscBuf[0]=='8') //Restore cursor and attributes
    {
        SetCursorR(mSaved.mCursor.r);
        SetCursorC(mSaved.mCursor.c);
        mCurAttr=mSaved.mCurAttr;
        CancelEscapeSequence();
    }
    else if(mEscBuf[0]=='D') //IND -- Index
    {
        CursorDown();
        CancelEscapeSequence();
    }
    else if(mEscBuf[0]=='H') //HTS -- Horizontal Tabulation Set
    {
        mTabStops.push_back(mCursor.c);
        CancelEscapeSequence();
    }
    else if(mEscBuf[0]=='(')
    {
        if(mEscBuf.length()==2)
        {
            switch(mEscBuf[1])
            {
            case '0':
                mGraphMode=true;
                break;
            default:
                mGraphMode=false;
            }
            CancelEscapeSequence();
        }
    }
    else if(mEscBuf[0]==')')
    {
        if(mEscBuf.length()==2)
        {
            switch(mEscBuf[1])
            {
            case '0':
                mGraphMode=true;
                break;
            default:
                mGraphMode=false;
            }
            CancelEscapeSequence();
        }
    }
    else if(mEscBuf[0]=='[')
    {
        if(IsValidCSIEnder(mEscBuf[mEscBuf.size()-1]))
        {
            InterpretCSISequence();
            CancelEscapeSequence();
        }
    }
    else if(mEscBuf[0]==']')
    {
        if(mEscBuf[mEscBuf.size()-1]=='\a')
        {
            InterpretXTermSequence();
            CancelEscapeSequence();
        }
    }
    else if(mEscBuf[0]=='#')
    {
        if(mEscBuf.length()==2)
        {
            switch(mEscBuf[1])
            {
            case '8':
                DoDECALN();
                break;
            default:
                msg_warn() << "unknown test command " << (char)mEscBuf[1] << "\n";
            }
            CancelEscapeSequence();
        }
    }
    else if(mEscBuf[0]=='='||mEscBuf[0]=='>') //set alternate/numeric keypad mode
    {
        //we can safely ignore this
        CancelEscapeSequence();
    }
    else
    {
        msg_warn() << "unrecognized escape sequence " << mEscBuf << "\n";
        CancelEscapeSequence();
        return;
    }
}

void CVirtualTerm::ProcessControlChar(char pChar)
{
    switch(pChar)
    {
    case '\r':  //carriage return
        SetCursorC(0);
        break;
    case '\n':  //line feed
    case 11:
    case 12:
        if(mMode&MODE_LINEFEED)
            mCursor.c=0;
        CursorDown();
        break;
    case '\b':  //backspace
        if(mCursor.c>0)
        {
            mCursor.c--;
            DirtyLine(mCursor.r);
        }
        break;
    case '\t':  //tab
        if(mTabStops.empty())
            mCursor.c=std::min(mSize.c-1,(mCursor.c/8+1)*8);
        else
        {
            bool lFound=false;
            for(int i=0;i<mTabStops.size();i++)
            {
                if(mTabStops[i]>mCursor.c)
                {
                    mCursor.c=mTabStops[i];
                    lFound=true;
                    break;
                }
            }
            if(!lFound) mCursor.c=mSize.c-1;
        }
        break;
    case 0x1B:  //begin escape sequence
        NewEscapeSequence();
        break;
    case 0x0E:  //begin graphical mode
        mGraphMode=true;
        break;
    case 0x0F:
        mGraphMode=false;
        break;
    case '\a':  //bell
        //TODO:something
        break;
    default:
        msg_warn() << "unrecognized control char\n";
    }
}

void CVirtualTerm::PutNormalChar(wchar_t pC)
{
    if(mCursor.c>=mSize.c)
    {
        if(mMode&MODE_AUTOWRAP)
        {
            if((mMode&MODE_ORIGIN)&&mCursor.r==mScrollBottom)
            {
                mCursor.c--;
            }
            else
            {
                mCursor.c=0;
                CursorDown();
            }
        }
        else
        {
            mCursor.c--;
        }
    }

    PLine lLine=GetLine(mCursor.r);

    if(mMode&MODE_INSERT)
    {
        std::rotate(lLine->mCells.begin()+mCursor.c,
           lLine->mCells.end()-1,lLine->mCells.end());
    }

    lLine->mCells[mCursor.c].mC=pC;
    lLine->mCells[mCursor.c].mAttr=mCurAttr;

    mCursor.c++;
    DirtyLine(mCursor.r);
}

void CVirtualTerm::PutGraphmodeChar(wchar_t pC)
{
    wchar_t lC=pC;

    switch(pC)
    {
    case 96:
        lC=0x25c6;
        break;
    case 'a':
        lC=0x2592;
        break;
    case 'b':
        lC=0x2409;
        break;
    case 'c':
        lC=0x240c;
        break;
    case 'd':
        lC=0x240d;
        break;
    case 'e':
        lC=0x240a;
        break;
    case 'f':
        lC=0x00b0;
        break;
    case 'g':
        lC=0x00b1;
        break;
    case 'h':
        lC=0x2424;
        break;
    case 'i':
        lC=0x240b;
        break;
    case 'j':
        lC=0x2518;
        break;
    case 'k':
        lC=0x2510;
        break;
    case 'l':
        lC=0x250c;
        break;
    case 'm':
        lC=0x2514;
        break;
    case 'n':
        lC=0x253c;
        break;
    case 'o':
        lC=0x23ba;
        break;
    case 'p':
        lC=0x23bb;
        break;
    case 'q':
        lC=0x2500;
        break;
    case 'r':
        lC=0x23bc;
        break;
    case 's':
        lC=0x23bd;
        break;
    case 't':
        lC=0x251c;
        break;
    case 'u':
        lC=0x2524;
        break;
    case 'v':
        lC=0x2534;
        break;
    case 'w':
        lC=0x252c;
        break;
    case 'x':
        lC=0x2502;
        break;
    case 'y':
        lC=0x2264;
        break;
    case 'z':
        lC=0x2265;
        break;
    case '{':
        lC=0x03c0;
        break;
    case '|':
        lC=0x2260;
        break;
    case '}':
        lC=0x00a3;
        break;
    case '~':
        lC=0x00b7;
        break;
    }
    PutNormalChar(lC);
}

void CVirtualTerm::SwitchAlternate()
{
    swap(mLines,mAlternate.mLines);

    if(mMode&MODE_ALTERNATE)    //into alternate mode
    {
        mAlternate.mSize=mSize; //save current size for later
        mAlternate.mCursor=mCursor;

        mLines.clear();
        mLines.set_capacity(mSize.r);

        for(int i=0;i<mSize.r;i++)
        {
            mLines.push_back(NewEmptyLine());
        }

        mCursor=mAlternate.mCursor;
    }
    else
    {
        if(mAlternate.mSize!=mSize)
        {
            CCursorPos lNewSize=mSize;
            mSize=mAlternate.mSize;
            mCursor=mAlternate.mCursor;

            if(mSize.r!=lNewSize.r)
                ResizeRows(lNewSize.r);
            if(mSize.c!=lNewSize.c)
                ResizeCols(lNewSize.c);
        }
    }

    sSizeChanged();
    DirtyLines(0,mSize.r-1);
}

void CVirtualTerm::ResizeRows(int pR)
{
    if(mMode&MODE_ALTERNATE)
    {
        mLines.set_capacity(pR);
    }

    if(mSize.r<pR)
    {
        for(int i=0;i<pR-mSize.r;i++)
        {
            mLines.push_back(NewEmptyLine());
        }
    }

    mSize.r=pR;

    if(mCursor.r>=mSize.r)
        mCursor.r=mSize.r-1;

    DirtyLines(0,mSize.r-1);
}

void CVirtualTerm::ResizeCols(int pC)
{
    //resize on-screen lines only
    for(TLineIter i=mLines.end()-mSize.r;i!=mLines.end();++i)
    {
        (*i)->mCells.resize(pC,sDefaultCell);
    }

    mSize.c=pC;

    if(mCursor.c>=mSize.c)
        mCursor.c=mSize.c-1;
    
    DirtyLines(0,mSize.r-1);
}

void CVirtualTerm::DoDECALN()
{
    for(TLineIter i=mLines.end()-mSize.r;i!=mLines.end();++i)
    {
        for(int j=0;j<mSize.c;j++)
        {
            (*i)->mCells[j].mC='E';
        }
    }
    
    DirtyLines(0,mSize.r-1);
}

void CVirtualTerm::DirtyLines(int pFrom,int pTo)
{
    if(!mSomeDirty)
    {
        mFirstDirty=pFrom;
        mLastDirty=pTo;
        mSomeDirty=true;
    }
    else
    {
        mFirstDirty=std::min(mFirstDirty,pFrom);
        mLastDirty=std::max(mLastDirty,pTo);
    }
}

/*namespace buola*/ }
